import { type ChildType, Component, cls, type StyledProps } from '@knno/jsx';
import { defineElement } from '../defineElement';
import { ANIMATION_DURATION, navigatorPageStyle, navigatorStyle } from './navView.css';

type CompleteHandler = (status: boolean) => void;

export type NavViewProps = StyledProps<NavView> & {
	key?: string;
	keepLastPage?: boolean;
	onPush?: (this: NavView) => void;
	onPop?: (this: NavView) => void;
};

let autoId = 10000;
const naviViews = new Map<string, NavView>();

const NaviElement = defineElement('knno-navi-view', HTMLDivElement, 'div');

export class NavView extends Component<NavViewProps> {
	static get(key: string): NavView | undefined {
		return naviViews.get(key);
	}

	constructor(props: NavViewProps) {
		super(props);
		if (!this.props.key) {
			this.props.key = `n${autoId++}`;
			return;
		}
		this.add(props.children ?? '');
	}

	protected render(): Node {
		return (
			<NaviElement
				class={cls(this.props.class, navigatorStyle)}
				style={this.props.style}
				onConnected={() => {
					naviViews.set(this.props.key!, this);
				}}
				onDisconnected={() => {
					naviViews.delete(this.props.key!);
				}}
			></NaviElement>
		);
	}

	private createPage(content: ChildType | ChildType[]): HTMLDivElement {
		const page = (<div class={navigatorPageStyle}>{content}</div>) as HTMLDivElement;
		return page;
	}

	push(content: ChildType | ChildType[], complete?: CompleteHandler): this;
	push(content: ChildType | ChildType[], replace: number, complete?: CompleteHandler): this;
	push(
		content: ChildType | ChildType[],
		replace?: number | CompleteHandler,
		complete?: CompleteHandler,
	): this {
		if (typeof replace === 'function') {
			complete = replace;
			replace = undefined;
		}
		const page = this.createPage(content);
		const lastPage = this.el.lastElementChild;
		this.el.append(page);
		setTimeout(() => {
			lastPage?.classList.add('inactive');
			page.classList.add('pushed');
		}, 32);
		setTimeout(() => {
			page.classList.add('ready');
			if (typeof complete === 'function' || typeof replace === 'number') {
				if (typeof replace === 'number' && !isNaN(replace)) {
					this.remove(replace, this.el.children.length - 1);
				}
				complete?.(true);
			}
			this.props.onPush?.call(this);
		}, ANIMATION_DURATION);
		return this;
	}

	add(content: ChildType | ChildType[], index?: number): this {
		if (typeof index !== 'number' || isNaN(index)) {
			index = this.el.children.length;
		}
		const page = this.createPage(content);
		page.classList.add('pushed', 'ready');
		if (index < this.el.children.length) {
			const old = this.el.children.item(index);
			page.classList.add('inactive');
			this.el.insertBefore(page, old);
		} else {
			const old = this.el.children.item(index - 1);
			old?.classList.add('inactive');
			this.el.appendChild(page);
		}
		return this;
	}

	remove(begin: number, end: number = -1): this {
		const children = Array.from(this.el.children);
		if (begin < 0) {
			begin = 0;
		}
		if (end > children.length || end < 0) {
			end = children.length;
		}
		const middlePages = children.slice(begin, end);
		middlePages.forEach((p) => p.remove());
		this.el.lastElementChild?.classList.remove('inactive');
		return this;
	}

	pop(complete?: CompleteHandler): this;
	pop(index: number, complete?: CompleteHandler): this;
	pop(index?: number | CompleteHandler, complete?: CompleteHandler): this {
		const children = Array.from(this.el.children).filter((el) => el.classList.contains('pushed'));
		if (typeof index !== 'number' || isNaN(index)) {
			index = children.length - 1;
		}
		if (typeof index === 'function') {
			complete = index;
		}

		const last = this.props.keepLastPage ? 1 : 0;
		if (index < last) {
			index = last;
		}
		if (index >= children.length) {
			complete?.(false);
			return this;
		}
		const previous = this.el.children[index - 1];
		const topest = children[children.length - 1];
		const middlePages = children.slice(index, children.length - 1);
		middlePages.forEach((p) => p.remove());
		previous.classList.remove('inactive');
		topest.classList.remove('pushed');
		setTimeout(() => {
			topest.remove();
			complete?.(true);
			this.props.onPop?.call(this);
		}, ANIMATION_DURATION);
		return this;
	}

	get keepLastPage(): boolean {
		return this.props.keepLastPage ?? false;
	}
	setKeepLastPage(value: boolean): this {
		this.props.keepLastPage = value;
		return this;
	}

	get pages(): number {
		return this.el.children.length;
	}
}
